/* Copyright (c) 2020 XEPIC Corporation Limited */
/*
 *  Copyright (C) 2011-2018  Cary R. (cygcary@yahoo.com)
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include <assert.h>
#include <inttypes.h>
#include <stdlib.h>

#include "ivl_alloc.h"
#include "sys_priv.h"

/*
 * The two queue types.
 */
#define IVL_QUEUE_FIFO 1
#define IVL_QUEUE_LIFO 2

/*
 * The statistical codes that can be passed to $q_exam().
 */
#define IVL_QUEUE_LENGTH 1
#define IVL_QUEUE_MEAN 2
#define IVL_QUEUE_MAX_LENGTH 3
#define IVL_QUEUE_SHORTEST 4
#define IVL_QUEUE_LONGEST 5
#define IVL_QUEUE_AVERAGE 6

/*
 * All the values that can be returned by the queue tasks/function.
 */
#define IVL_QUEUE_OK 0
#define IVL_QUEUE_FULL 1
#define IVL_QUEUE_UNDEFINED_ID 2
#define IVL_QUEUE_EMPTY 3
#define IVL_QUEUE_UNSUPPORTED_TYPE 4
#define IVL_QUEUE_INVALID_LENGTH 5
#define IVL_QUEUE_DUPLICATE_ID 6
#define IVL_QUEUE_OUT_OF_MEMORY 7
/* Icarus specific status codes. */
#define IVL_QUEUE_UNDEFINED_STAT_CODE 8
#define IVL_QUEUE_VALUE_OVERFLOWED 9
#define IVL_QUEUE_NO_STATISTICS 10

/*
 * Routine to add the given time to the the total time (high/low).
 */
static void add_to_wait_time(uint64_t *high, uint64_t *low, uint64_t c_time) {
  uint64_t carry = 0U;

  if ((UINT64_MAX - *low) < c_time) carry = 1U;
  *low += c_time;
  assert((carry == 0U) || (*high < UINT64_MAX));
  *high += carry;
}

/*
 * Routine to divide the given total time (high/low) by the number of
 * items to get the average.
 */
static uint64_t calc_average_wait_time(uint64_t high, uint64_t low,
                                       uint64_t total) {
  int bit = 64;
  uint64_t result = 0U;
  assert(total != 0U);
  if (high == 0U) return (low / total);

  /* This is true by design, but since we can only return 64 bits
   * make sure nothing went wrong. */
  assert(high < total);

  /* It's a big value so calculate the average the long way. */
  do {
    unsigned carry = 0U;
    /* Copy bits from low to high until we have a bit to place
     * in the result or there are no bits left. */
    while ((bit >= 0) && (high < total) && !carry) {
      /* If the MSB is set then we will have a carry. */
      if (high > (UINT64_MAX >> 1)) carry = 1U;
      high <<= 1;
      high |= (low & 0x8000000000000000) != 0;
      low <<= 1;
      bit -= 1;
    }

    /* If this is a valid bit, set the appropriate bit in the
     * result and subtract the total from the current value. */
    if (bit >= 0) {
      result |= UINT64_C(1) << bit;
      high = high - total;
    }
    /* Loop until there are no bits left. */
  } while (bit > 0);

  return result;
}

/*
 * The data structure used for an individual queue element. It hold four
 * state result for the jobs and inform fields along with the time that
 * the element was added in base time units.
 */
typedef struct t_ivl_queue_elem {
  uint64_t time;
  s_vpi_vecval job;
  s_vpi_vecval inform;
} s_ivl_queue_elem, *p_ivl_queue_elem;

/*
 * This structure is used to represent a specific queue. The time
 * information is in base simulation units.
 */
typedef struct t_ivl_queue_base {
  uint64_t shortest_wait_time;
  uint64_t first_add_time;
  uint64_t latest_add_time;
  uint64_t wait_time_high;
  uint64_t wait_time_low;
  uint64_t number_of_adds;
  p_ivl_queue_elem queue;
  PLI_INT32 id;
  PLI_INT32 length;
  PLI_INT32 type;
  PLI_INT32 head;
  PLI_INT32 elems;
  PLI_INT32 max_len;
  PLI_INT32 have_shortest_statistic;
} s_ivl_queue_base, *p_ivl_queue_base;

/*
 * For now we keep the queues in a vector since there are likely not too many
 * of them. We may need something more efficient later.
 */
static p_ivl_queue_base base = NULL;
static int64_t base_len = 0;

/*
 * This routine is called at the end of simulation to free the queue memory.
 */
static PLI_INT32 cleanup_queue(p_cb_data cause) {
  PLI_INT32 idx;
  (void)cause; /* Unused argument. */
  for (idx = 0; idx < base_len; idx += 1) free(base[idx].queue);
  free(base);
  base = NULL;
  base_len = 0;
  return 0;
}

/*
 * Add a new queue to the list, return 1 if there is not enough memory,
 * otherwise return 0.
 */
static unsigned create_queue(PLI_INT32 id, PLI_INT32 type, PLI_INT32 length) {
  p_ivl_queue_base new_base;
  p_ivl_queue_elem queue;

  /* Allocate space for the new queue base. */
  base_len += 1;
  new_base =
      (p_ivl_queue_base)realloc(base, base_len * sizeof(s_ivl_queue_base));

  /* If we ran out of memory then fix the length and return a fail. */
  if (new_base == NULL) {
    base_len -= 1;
    return 1;
  }
  base = new_base;

  /* Allocate space for the queue elements. */
  queue = (p_ivl_queue_elem)malloc(length * sizeof(s_ivl_queue_elem));

  /* If we ran out of memory then fix the length and return a fail. */
  if (queue == NULL) {
    base_len -= 1;
    return 1;
  }

  /* The memory was allocated so configure it. */
  base[base_len - 1].queue = queue;
  base[base_len - 1].id = id;
  base[base_len - 1].length = length;
  base[base_len - 1].type = type;
  base[base_len - 1].head = 0;
  base[base_len - 1].elems = 0;
  base[base_len - 1].max_len = 0;
  base[base_len - 1].shortest_wait_time = UINT64_MAX;
  base[base_len - 1].first_add_time = 0U;
  base[base_len - 1].latest_add_time = 0U;
  base[base_len - 1].wait_time_high = 0U;
  base[base_len - 1].wait_time_low = 0U;
  base[base_len - 1].number_of_adds = 0U;
  base[base_len - 1].have_shortest_statistic = 0;
  return 0;
}

/*
 * Check to see if the given queue is full.
 */
static unsigned is_queue_full(int64_t idx) {
  if (base[idx].elems >= base[idx].length) return 1;

  return 0;
}

/*
 * Add the job and inform to the queue. Return 1 if the queue is full,
 * otherwise return 0.
 */
static unsigned add_to_queue(int64_t idx, p_vpi_vecval job,
                             p_vpi_vecval inform) {
  PLI_INT32 length = base[idx].length;
  PLI_INT32 type = base[idx].type;
  PLI_INT32 head = base[idx].head;
  PLI_INT32 elems = base[idx].elems;
  PLI_INT32 loc;
  s_vpi_time cur_time;
  uint64_t c_time;

  assert(elems <= length);

  /* If the queue is full we can't add anything. */
  if (elems == length) return 1;

  /* Increment the number of element since one will be added.*/
  base[idx].elems += 1;

  /* Save the job and inform to the queue. */
  if (type == IVL_QUEUE_LIFO) {
    assert(head == 0); /* For a LIFO head must always be zero. */
    loc = elems;
  } else {
    assert(type == IVL_QUEUE_FIFO);
    loc = head + elems;
    if (loc >= length) loc -= length;
  }
  base[idx].queue[loc].job.aval = job->aval;
  base[idx].queue[loc].job.bval = job->bval;
  base[idx].queue[loc].inform.aval = inform->aval;
  base[idx].queue[loc].inform.bval = inform->bval;

  /* Save the current time with this entry for the statistics. */
  cur_time.type = vpiSimTime;
  vpi_get_time(NULL, &cur_time);
  c_time = cur_time.high;
  c_time <<= 32;
  c_time |= cur_time.low;
  base[idx].queue[loc].time = c_time;

  /* Increment the maximum length if needed. */
  if (base[idx].max_len == elems) base[idx].max_len += 1;

  /* Update the inter-arrival statistics. */
  assert(base[idx].number_of_adds < UINT64_MAX);
  base[idx].number_of_adds += 1;
  if (base[idx].number_of_adds == 1) base[idx].first_add_time = c_time;
  base[idx].latest_add_time = c_time;

  return 0;
}

/*
 * Get the job and inform values from the queue. Return 1 if the queue is
 * empty, otherwise return 0.
 */
static unsigned remove_from_queue(int64_t idx, p_vpi_vecval job,
                                  p_vpi_vecval inform) {
  PLI_INT32 type = base[idx].type;
  PLI_INT32 head = base[idx].head;
  PLI_INT32 elems = base[idx].elems - 1;
  PLI_INT32 loc;
  s_vpi_time cur_time;
  uint64_t c_time;

  assert(elems >= -1);

  /* If the queue is empty we can't remove anything. */
  if (elems < 0) return 1;

  /* Decrement the number of element in the queue structure since one
   * will be removed.*/
  base[idx].elems -= 1;

  /* Remove the job and inform from the queue. */
  if (type == IVL_QUEUE_LIFO) {
    assert(head == 0); /* For a LIFO head must always be zero. */
    loc = elems;
  } else {
    assert(type == IVL_QUEUE_FIFO);
    loc = head;
    if (head + 1 == base[idx].length)
      base[idx].head = 0;
    else
      base[idx].head += 1;
  }
  job->aval = base[idx].queue[loc].job.aval;
  job->bval = base[idx].queue[loc].job.bval;
  inform->aval = base[idx].queue[loc].inform.aval;
  inform->bval = base[idx].queue[loc].inform.bval;

  /* Get the current simulation time. */
  cur_time.type = vpiSimTime;
  vpi_get_time(NULL, &cur_time);
  c_time = cur_time.high;
  c_time <<= 32;
  c_time |= cur_time.low;

  /* Set the shortest wait time if needed. */
  assert(c_time >= base[idx].queue[loc].time);
  c_time -= base[idx].queue[loc].time;
  if (c_time < base[idx].shortest_wait_time) {
    base[idx].shortest_wait_time = c_time;
  }
  base[idx].have_shortest_statistic = 1;

  /* Add the current element wait time to the total wait time. */
  add_to_wait_time(&(base[idx].wait_time_high), &(base[idx].wait_time_low),
                   c_time);

  return 0;
}

/*
 * Return the current queue length.
 */
static PLI_INT32 get_current_queue_length(int64_t idx) {
  return base[idx].elems;
}

/*
 * Return the maximum queue length.
 */
static PLI_INT32 get_maximum_queue_length(int64_t idx) {
  return base[idx].max_len;
}

/*
 * Return the longest wait time in the queue in base simulation units.
 * Make sure to check that there are elements in the queue before calling
 * this routine. The caller will need to scale the time as appropriate.
 */
static uint64_t get_longest_queue_time(int64_t idx) {
  s_vpi_time cur_time;
  uint64_t c_time;

  /* Get the current simulation time. */
  cur_time.type = vpiSimTime;
  vpi_get_time(NULL, &cur_time);
  c_time = cur_time.high;
  c_time <<= 32;
  c_time |= cur_time.low;

  /* Subtract the element with the longest time (the head) from the
   * current time. */
  assert(c_time >= base[idx].queue[base[idx].head].time);
  c_time -= base[idx].queue[base[idx].head].time;

  return c_time;
}

/*
 * Check to see if there are inter-arrival time statistics.
 */
static unsigned have_interarrival_statistic(int64_t idx) {
  return (base[idx].number_of_adds >= 2U);
}

/*
 * Return the mean inter-arrival time for the queue. This is just the
 * latest add time minus the first add time divided be the number of time
 * deltas (the number of adds - 1).
 */
static uint64_t get_mean_interarrival_time(int64_t idx) {
  return ((base[idx].latest_add_time - base[idx].first_add_time) /
          (base[idx].number_of_adds - 1U));
}

/*
 * Check to see if there are shortest wait time statistics.
 */
static unsigned have_shortest_wait_statistic(int64_t idx) {
  return (base[idx].have_shortest_statistic != 0);
}

/*
 * Return the shortest amount of time an element has waited in the queue.
 */
static uint64_t get_shortest_wait_time(int64_t idx) {
  return base[idx].shortest_wait_time;
}

/*
 * Check to see if we have an average wait time statistics.
 */
static unsigned have_average_wait_statistic(int64_t idx) {
  return (base[idx].number_of_adds >= 1U);
}

/*
 * Return the average wait time in the queue.
 */
static uint64_t get_average_wait_time(int64_t idx) {
  PLI_INT32 length = base[idx].length;
  PLI_INT32 loc = base[idx].head;
  PLI_INT32 elems = base[idx].elems;
  PLI_INT32 count;
  /* Initialize the high and low time with the current total time. */
  uint64_t high = base[idx].wait_time_high;
  uint64_t low = base[idx].wait_time_low;
  s_vpi_time cur_time;
  uint64_t c_time;

  /* Get the current simulation time. */
  cur_time.type = vpiSimTime;
  vpi_get_time(NULL, &cur_time);
  c_time = cur_time.high;
  c_time <<= 32;
  c_time |= cur_time.low;

  /* For each element still in the queue, add its wait time to the
   * total wait time. */
  for (count = 0; count < elems; count += 1) {
    uint64_t add_time = base[idx].queue[loc].time;
    assert(c_time >= add_time);
    add_to_wait_time(&high, &low, c_time - add_time);

    /* Move to the next element. */
    loc += 1;
    if (loc == length) loc = 0;
  }

  /* Return the average wait time. */
  return calc_average_wait_time(high, low, base[idx].number_of_adds);
}

/*
 * Check to see if the given id already exists. Return the index for the
 * queue if it exists, otherwise return -1.
 */
static int64_t get_id_index(PLI_INT32 id) {
  int64_t idx;

  for (idx = 0; idx < base_len; idx += 1) {
    if (id == base[idx].id) return idx;
  }

  return -1;
}

/*
 * Check to see if the given value is bit based and has 32 or fewer bits.
 */
static unsigned is_32_or_smaller_obj(vpiHandle obj) {
  PLI_INT32 const_type;
  unsigned rtn = 0;

  assert(obj);

  switch (vpi_get(vpiType, obj)) {
    case vpiConstant:
    case vpiParameter:
      const_type = vpi_get(vpiConstType, obj);
      if ((const_type != vpiRealConst) && (const_type != vpiStringConst))
        rtn = 1;
      break;

      /* These can have valid 32 bit or smaller numeric values. */
    case vpiIntegerVar:
    case vpiBitVar:
    case vpiByteVar:
    case vpiShortIntVar:
    case vpiIntVar:
    case vpiMemoryWord:
    case vpiNet:
    case vpiPartSelect:
    case vpiReg:
      rtn = 1;
      break;
  }

  /* The object must be 32 bits or smaller. */
  if (vpi_get(vpiSize, obj) > 32) rtn = 0;

  return rtn;
}

/*
 * Check to see if the argument is a variable that is exactly 32 bits in size.
 */
static void check_var_arg_32(vpiHandle arg, vpiHandle callh, const char *name,
                             const char *desc) {
  assert(arg);

  switch (vpi_get(vpiType, arg)) {
    case vpiMemoryWord:
    case vpiPartSelect:
    case vpiBitVar:
    case vpiReg:  // Check that we have exactly 32 bits.
      if (vpi_get(vpiSize, arg) != 32) {
        vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
                   (int)vpi_get(vpiLineNo, callh));
        vpi_printf("%s's %s (variable) argument must be 32 bits.\n", name,
                   desc);
        vpi_control(vpiFinish, 1);
      }
    case vpiIntegerVar:
    case vpiIntVar:
      break;
    default:
      vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
                 (int)vpi_get(vpiLineNo, callh));
      vpi_printf("%s's %s argument must be a 32 bit variable.\n", name, desc);
      vpi_control(vpiFinish, 1);
  }
}

/*
 * Check to see if the argument is a variable of at least 32 bits.
 */
static void check_var_arg_large(vpiHandle arg, vpiHandle callh,
                                const char *name, const char *desc) {
  assert(arg);

  switch (vpi_get(vpiType, arg)) {
    case vpiMemoryWord:
    case vpiPartSelect:
    case vpiBitVar:
    case vpiReg:  // Check that we have at least 32 bits.
      if (vpi_get(vpiSize, arg) < 32) {
        vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
                   (int)vpi_get(vpiLineNo, callh));
        vpi_printf(
            "%s's %s (variable) argument must have at least "
            "32 bits.\n",
            name, desc);
        vpi_control(vpiFinish, 1);
      }
    case vpiIntegerVar:
    case vpiIntVar:
    case vpiLongIntVar:
    case vpiTimeVar:
      break;
    default:
      vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
                 (int)vpi_get(vpiLineNo, callh));
      vpi_printf("%s's %s argument must be a variable.\n", name, desc);
      vpi_control(vpiFinish, 1);
  }
}

/*
 * Check that the given number of arguments are numeric.
 */
static unsigned check_numeric_args(vpiHandle argv, unsigned count,
                                   vpiHandle callh, const char *name) {
  unsigned idx;

  /* Check that the first count arguments are numeric. Currently
   * only three are needed/supported. */
  for (idx = 0; idx < count; idx += 1) {
    const char *loc = NULL;
    vpiHandle arg = vpi_scan(argv);

    /* Get the name for this argument. */
    switch (idx) {
      case 0:
        loc = "first";
        break;
      case 1:
        loc = "second";
        break;
      case 2:
        loc = "third";
        break;
      default:
        assert(0);
    }

    /* Check that there actually is an argument. */
    if (!arg) {
      vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
                 (int)vpi_get(vpiLineNo, callh));
      vpi_printf("%s requires a %s (<= 32 bit numeric) argument.\n", name, loc);
      vpi_control(vpiFinish, 1);
      return 1;
    }

    /* Check that it is no more than 32 bits. */
    if (!is_32_or_smaller_obj(arg)) {
      vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
                 (int)vpi_get(vpiLineNo, callh));
      vpi_printf("%s's %s argument must be numeric (<= 32 bits).\n", name, loc);
      vpi_control(vpiFinish, 1);
    }
  }

  return 0;
}

/*
 * Check to see if the given argument is valid (does not have any X/Z bits).
 * Return zero if it is valid and a positive value if it is invalid.
 */
static unsigned get_valid_32(vpiHandle arg, PLI_INT32 *value) {
  PLI_INT32 size, mask;
  s_vpi_value val;

  size = vpi_get(vpiSize, arg);
  /* The compiletf routine should have already verified that this is
   * <= 32 bits. */
  assert((size <= 32) && (size > 0));

  /* Create a mask so that we only check the appropriate bits. */
  mask = UINT32_MAX >> (32 - size);

  /* Get the value and return the possible integer value in the value
   * variable. Return the b-value bits to indicate if the value is
   * undefined (has X/Z bit). */
  val.format = vpiVectorVal;
  vpi_get_value(arg, &val);

  *value = val.value.vector->aval & mask;
  /* If the argument is signed and less than 32 bit we need to sign
   * extend the value. */
  if (vpi_get(vpiSigned, arg) && (size < 32)) {
    if ((*value) & (1 << (size - 1))) *value |= ~mask;
  }
  return (val.value.vector->bval & mask);
}

static void get_four_state(vpiHandle arg, p_vpi_vecval vec) {
  PLI_INT32 size, mask;
  s_vpi_value val;

  size = vpi_get(vpiSize, arg);
  /* The compiletf routine should have already verified that this is
   * <= 32 bits. */
  assert((size <= 32) && (size > 0));

  /* Create a mask so that we only use the appropriate bits. */
  mask = UINT32_MAX >> (32 - size);

  /* Get the bits for the argument and save them in the return value. */
  val.format = vpiVectorVal;
  vpi_get_value(arg, &val);
  vec->aval = val.value.vector->aval & mask;
  vec->bval = val.value.vector->bval & mask;

  /* If the argument is signed and less than 32 bit we need to sign
   * extend the value. */
  if (vpi_get(vpiSigned, arg) && (size < 32)) {
    if (vec->aval & (1 << (size - 1))) vec->aval |= ~mask;
    if (vec->bval & (1 << (size - 1))) vec->bval |= ~mask;
  }
}

/*
 * Fill the passed variable with x.
 */
static void fill_variable_with_x(vpiHandle var) {
  s_vpi_value val;
  PLI_INT32 words = ((vpi_get(vpiSize, var) - 1) / 32) + 1;
  PLI_INT32 idx;
  p_vpi_vecval val_ptr = (p_vpi_vecval)malloc(words * sizeof(s_vpi_vecval));

  assert(val_ptr);

  /* Fill the vector with X. */
  for (idx = 0; idx < words; idx += 1) {
    val_ptr[idx].aval = 0xffffffff;
    val_ptr[idx].bval = 0xffffffff;
  }

  /* Put the vector to the variable. */
  val.format = vpiVectorVal;
  val.value.vector = val_ptr;
  vpi_put_value(var, &val, 0, vpiNoDelay);
  free(val_ptr);
}

/*
 * Fill the passed variable with the passed value if it fits. If it doesn't
 * fit then set all bits to one and return that the value is too big instead
 * of the normal OK. The value is a time and needs to be scaled to the
 * calling module's timescale.
 */
static PLI_INT32 fill_variable_with_scaled_time(vpiHandle var,
                                                uint64_t c_time) {
  s_vpi_value val;
  PLI_INT32 size = vpi_get(vpiSize, var);
  PLI_INT32 is_signed = vpi_get(vpiSigned, var);
  PLI_INT32 words = ((size - 1) / 32) + 1;
  uint64_t max_val = 0;
  uint64_t scale = 1;
  uint64_t frac;
  PLI_INT32 rtn, units, prec;
  p_vpi_vecval val_ptr = (p_vpi_vecval)malloc(words * sizeof(s_vpi_vecval));

  assert(val_ptr);
  assert(size >= 32);
  assert(words > 0);

  /* Scale the variable to match the calling module's timescale. */
  prec = vpi_get(vpiTimePrecision, 0);
  units = vpi_get(vpiTimeUnit, vpi_handle(vpiModule, var));
  assert(units >= prec);
  while (units > prec) {
    scale *= 10;
    units -= 1;
  }
  frac = c_time % scale;
  c_time /= scale;
  if ((scale > 1) && (frac >= scale / 2)) c_time += 1;

  /* Find the maximum value + 1 that can be put into the variable. */
  if (size < 64) {
    max_val = 1;
    max_val <<= (size - is_signed);
  }

  /* If the time is too big to fit then return the maximum positive
   * value and that the value overflowed. Otherwise, return the time
   * and OK. */
  if (max_val && (c_time >= max_val)) {
    /* For a single word only the MSB is cleared if signed. */
    if (words == 1) {
      if (is_signed) {
        val_ptr[0].aval = 0x7fffffff;
      } else {
        val_ptr[0].aval = 0xffffffff;
      }
      val_ptr[0].bval = 0x00000000;
      /* For two words the lower word is filled with 1 and the top
       * word has a size dependent fill if signed. */
    } else {
      assert(words == 2);
      val_ptr[0].aval = 0xffffffff;
      val_ptr[0].bval = 0x00000000;
      if (is_signed) {
        val_ptr[1].aval = ~(UINT32_MAX >> (size - 32));
      } else {
        val_ptr[1].aval = 0xffffffff;
      }
      val_ptr[1].bval = 0x00000000;
    }
    rtn = IVL_QUEUE_VALUE_OVERFLOWED;
  } else {
    /* Fill the vector with 0. */
    for (PLI_INT32 idx = 0; idx < words; idx += 1) {
      val_ptr[idx].aval = 0x00000000;
      val_ptr[idx].bval = 0x00000000;
    }
    /* Add the time to the vector. */
    switch (words) {
      default:
        val_ptr[1].aval = (c_time >> 32) & 0xffffffff;
        // fallthrough
      case 1:
        val_ptr[0].aval = c_time & 0xffffffff;
    }
    rtn = IVL_QUEUE_OK;
  }

  /* Put the vector to the variable. */
  val.format = vpiVectorVal;
  val.value.vector = val_ptr;
  vpi_put_value(var, &val, 0, vpiNoDelay);
  free(val_ptr);

  return rtn;
}

/*
 * Check that the given $q_initialize() call has valid arguments.
 */
static PLI_INT32 sys_q_initialize_compiletf(ICARUS_VPI_CONST PLI_BYTE8 *name) {
  vpiHandle callh = vpi_handle(vpiSysTfCall, 0);
  vpiHandle argv = vpi_iterate(vpiArgument, callh);
  vpiHandle arg;

  /* Check that there are arguments. */
  if (argv == 0) {
    vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("%s requires four arguments.\n", name);
    vpi_control(vpiFinish, 1);
    return 0;
  }

  /* Check that the first three arguments (the id, type and maximum
   * length) are numeric. */
  if (check_numeric_args(argv, 3, callh, name)) return 0;

  /* The fourth argument (the status) must be a variable. */
  arg = vpi_scan(argv);
  if (!arg) {
    vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("%s requires a fourth (variable) argument.\n", name);
    vpi_control(vpiFinish, 1);
    return 0;
  }
  /* Check that the status argument is a 32 bit variable. */
  check_var_arg_32(arg, callh, name, "fourth");

  /* Make sure there are no extra arguments. */
  check_for_extra_args(argv, callh, name, "four arguments", 0);

  return 0;
}

/*
 * The runtime code for $q_initialize().
 */
static PLI_INT32 sys_q_initialize_calltf(ICARUS_VPI_CONST PLI_BYTE8 *name) {
  vpiHandle callh = vpi_handle(vpiSysTfCall, 0);
  vpiHandle argv = vpi_iterate(vpiArgument, callh);
  vpiHandle status;
  PLI_INT32 id, type, length;
  s_vpi_value val;
  unsigned invalid_id, invalid_type, invalid_length;

  (void)name; /* Parameter is not used. */

  /* Get the id. */
  invalid_id = get_valid_32(vpi_scan(argv), &id);

  /* Get the queue type. */
  invalid_type = get_valid_32(vpi_scan(argv), &type);

  /* Get the queue maximum length. */
  invalid_length = get_valid_32(vpi_scan(argv), &length);

  /* Get the status variable. */
  status = vpi_scan(argv);

  /* We are done with the argument iterator so free it. */
  vpi_free_object(argv);

  /* If the id is invalid then return. */
  if (invalid_id) {
    val.format = vpiIntVal;
    val.value.integer = IVL_QUEUE_UNDEFINED_ID;
    vpi_put_value(status, &val, 0, vpiNoDelay);
    return 0;
  }

  /* Verify that the type is valid. */
  if (invalid_type || ((type != IVL_QUEUE_FIFO) && (type != IVL_QUEUE_LIFO))) {
    val.format = vpiIntVal;
    val.value.integer = IVL_QUEUE_UNSUPPORTED_TYPE;
    vpi_put_value(status, &val, 0, vpiNoDelay);
    return 0;
  }

  /* Verify that the queue length is greater than zero. */
  if (invalid_length || (length <= 0)) {
    val.format = vpiIntVal;
    val.value.integer = IVL_QUEUE_INVALID_LENGTH;
    vpi_put_value(status, &val, 0, vpiNoDelay);
    return 0;
  }

  /* Check that this is not a duplicate queue id. */
  if (get_id_index(id) >= 0) {
    val.format = vpiIntVal;
    val.value.integer = IVL_QUEUE_DUPLICATE_ID;
    vpi_put_value(status, &val, 0, vpiNoDelay);
    return 0;
  }

  /* Create the queue and fail if we do not have enough memory. */
  if (create_queue(id, type, length)) {
    val.format = vpiIntVal;
    val.value.integer = IVL_QUEUE_OUT_OF_MEMORY;
    vpi_put_value(status, &val, 0, vpiNoDelay);
    return 0;
  }

  /* The queue was initialized correctly so return OK. */
  val.format = vpiIntVal;
  val.value.integer = IVL_QUEUE_OK;
  vpi_put_value(status, &val, 0, vpiNoDelay);
  return 0;
}

/*
 * Check that the given $q_add() call has valid arguments.
 */
static PLI_INT32 sys_q_add_compiletf(ICARUS_VPI_CONST PLI_BYTE8 *name) {
  vpiHandle callh = vpi_handle(vpiSysTfCall, 0);
  vpiHandle argv = vpi_iterate(vpiArgument, callh);
  vpiHandle arg;

  /* Check that there are arguments. */
  if (argv == 0) {
    vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("%s requires four arguments.\n", name);
    vpi_control(vpiFinish, 1);
    return 0;
  }

  /* Check that the first three arguments (the id, job and information)
   * are numeric. */
  if (check_numeric_args(argv, 3, callh, name)) return 0;

  /* The fourth argument (the status) must be a variable. */
  arg = vpi_scan(argv);
  if (!arg) {
    vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("%s requires a fourth (variable) argument.\n", name);
    vpi_control(vpiFinish, 1);
    return 0;
  }
  /* Check that the status argument is a 32 bit variable. */
  check_var_arg_32(arg, callh, name, "fourth");

  /* Make sure there are no extra arguments. */
  check_for_extra_args(argv, callh, name, "four arguments", 0);

  return 0;
}

/*
 * The runtime code for $q_add().
 */
static PLI_INT32 sys_q_add_calltf(ICARUS_VPI_CONST PLI_BYTE8 *name) {
  vpiHandle callh = vpi_handle(vpiSysTfCall, 0);
  vpiHandle argv = vpi_iterate(vpiArgument, callh);
  vpiHandle status;
  PLI_INT32 id;
  int64_t idx;
  s_vpi_vecval job, inform;
  s_vpi_value val;
  unsigned invalid_id;

  (void)name; /* Parameter is not used. */

  /* Get the id. */
  invalid_id = get_valid_32(vpi_scan(argv), &id);

  /* Get the job. */
  get_four_state(vpi_scan(argv), &job);

  /* Get the value. */
  get_four_state(vpi_scan(argv), &inform);

  /* Get the status variable. */
  status = vpi_scan(argv);

  /* We are done with the argument iterator so free it. */
  vpi_free_object(argv);

  /* Verify that the id is valid. */
  idx = get_id_index(id);
  if (invalid_id || (idx < 0)) {
    val.format = vpiIntVal;
    val.value.integer = IVL_QUEUE_UNDEFINED_ID;
    vpi_put_value(status, &val, 0, vpiNoDelay);
    return 0;
  }

  /* Add the data to the queue if it is not already full. */
  if (add_to_queue(idx, &job, &inform)) {
    val.format = vpiIntVal;
    val.value.integer = IVL_QUEUE_FULL;
    vpi_put_value(status, &val, 0, vpiNoDelay);
    return 0;
  }

  /* The data was added to the queue so return OK. */
  val.format = vpiIntVal;
  val.value.integer = IVL_QUEUE_OK;
  vpi_put_value(status, &val, 0, vpiNoDelay);
  return 0;
}

/*
 * Check that the given $q_remove() call has valid arguments.
 */
static PLI_INT32 sys_q_remove_compiletf(ICARUS_VPI_CONST PLI_BYTE8 *name) {
  vpiHandle callh = vpi_handle(vpiSysTfCall, 0);
  vpiHandle argv = vpi_iterate(vpiArgument, callh);
  vpiHandle arg;

  /* Check that there are arguments. */
  if (argv == 0) {
    vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("%s requires four arguments.\n", name);
    vpi_control(vpiFinish, 1);
    return 0;
  }

  /* The first argument (the id) must be numeric. */
  if (!is_32_or_smaller_obj(vpi_scan(argv))) {
    vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("%s's first argument must be numeric (<= 32 bits).\n", name);
    vpi_control(vpiFinish, 1);
  }

  /* The second argument (the job id) must be a variable. */
  arg = vpi_scan(argv);
  if (!arg) {
    vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("%s requires a second (variable) argument.\n", name);
    vpi_control(vpiFinish, 1);
    return 0;
  }
  /* Check that the job id argument is a 32 bit variable. */
  check_var_arg_32(arg, callh, name, "second");

  /* The third argument (the information id) must be a variable. */
  arg = vpi_scan(argv);
  if (!arg) {
    vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("%s requires a third (variable) argument.\n", name);
    vpi_control(vpiFinish, 1);
    return 0;
  }
  /* Check that the information id argument is a 32 bit variable. */
  check_var_arg_32(arg, callh, name, "third");

  /* The fourth argument (the status) must be a variable. */
  arg = vpi_scan(argv);
  if (!arg) {
    vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("%s requires a fourth (variable) argument.\n", name);
    vpi_control(vpiFinish, 1);
    return 0;
  }
  /* Check that the status argument is a 32 bit variable. */
  check_var_arg_32(arg, callh, name, "fourth");

  /* Make sure there are no extra arguments. */
  check_for_extra_args(argv, callh, name, "four arguments", 0);

  return 0;
}

/*
 * The runtime code for $q_remove().
 */
static PLI_INT32 sys_q_remove_calltf(ICARUS_VPI_CONST PLI_BYTE8 *name) {
  vpiHandle callh = vpi_handle(vpiSysTfCall, 0);
  vpiHandle argv = vpi_iterate(vpiArgument, callh);
  vpiHandle job, inform, status;
  PLI_INT32 id, idx;
  s_vpi_vecval job_val, inform_val;
  s_vpi_value val;
  unsigned invalid_id;

  (void)name; /* Parameter is not used. */

  /* Get the id. */
  invalid_id = get_valid_32(vpi_scan(argv), &id);

  /* Get the job variable. */
  job = vpi_scan(argv);

  /* Get the inform variable. */
  inform = vpi_scan(argv);

  /* Get the status variable. */
  status = vpi_scan(argv);

  /* We are done with the argument iterator so free it. */
  vpi_free_object(argv);

  /* Verify that the id is valid. */
  idx = get_id_index(id);
  if (invalid_id || (idx < 0)) {
    fill_variable_with_x(job);
    fill_variable_with_x(inform);
    val.format = vpiIntVal;
    val.value.integer = IVL_QUEUE_UNDEFINED_ID;
    vpi_put_value(status, &val, 0, vpiNoDelay);
    return 0;
  }

  /* Remove the data from the queue if it is not already empty. */
  if (remove_from_queue(idx, &job_val, &inform_val)) {
    fill_variable_with_x(job);
    fill_variable_with_x(inform);
    val.format = vpiIntVal;
    val.value.integer = IVL_QUEUE_EMPTY;
    vpi_put_value(status, &val, 0, vpiNoDelay);
    return 0;
  }

  val.format = vpiVectorVal;
  val.value.vector = &job_val;
  vpi_put_value(job, &val, 0, vpiNoDelay);
  val.format = vpiVectorVal;
  val.value.vector = &inform_val;
  vpi_put_value(inform, &val, 0, vpiNoDelay);

  /* The data was added to the queue so return OK. */
  val.format = vpiIntVal;
  val.value.integer = IVL_QUEUE_OK;
  vpi_put_value(status, &val, 0, vpiNoDelay);
  return 0;
}

/*
 * Check that the given $q_full() call has valid arguments.
 */
static PLI_INT32 sys_q_full_compiletf(ICARUS_VPI_CONST PLI_BYTE8 *name) {
  vpiHandle callh = vpi_handle(vpiSysTfCall, 0);
  vpiHandle argv = vpi_iterate(vpiArgument, callh);
  vpiHandle arg;

  /* Check that there are arguments. */
  if (argv == 0) {
    vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("%s requires two arguments.\n", name);
    vpi_control(vpiFinish, 1);
    return 0;
  }

  /* The first argument (the id) must be numeric. */
  if (!is_32_or_smaller_obj(vpi_scan(argv))) {
    vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("%s's first argument must be numeric (<= 32 bits).\n", name);
    vpi_control(vpiFinish, 1);
  }

  /* The second argument (the status) must be a variable. */
  arg = vpi_scan(argv);
  if (!arg) {
    vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("%s requires a second (variable) argument.\n", name);
    vpi_control(vpiFinish, 1);
    return 0;
  }
  /* Check that the status argument is a 32 bit variable. */
  check_var_arg_32(arg, callh, name, "second");

  /* Make sure there are no extra arguments. */
  check_for_extra_args(argv, callh, name, "two arguments", 0);

  return 0;
}

/*
 * The runtime code for $q_full().
 */
static PLI_INT32 sys_q_full_calltf(ICARUS_VPI_CONST PLI_BYTE8 *name) {
  vpiHandle callh = vpi_handle(vpiSysTfCall, 0);
  vpiHandle argv = vpi_iterate(vpiArgument, callh);
  vpiHandle status;
  PLI_INT32 id, idx;
  s_vpi_value val;
  unsigned invalid_id;

  (void)name; /* Parameter is not used. */

  /* Get the id. */
  invalid_id = get_valid_32(vpi_scan(argv), &id);

  /* Get the status variable. */
  status = vpi_scan(argv);

  /* We are done with the argument iterator so free it. */
  vpi_free_object(argv);

  /* Verify that the id is valid. */
  idx = get_id_index(id);
  if (invalid_id || (idx < 0)) {
    val.format = vpiIntVal;
    val.value.integer = IVL_QUEUE_UNDEFINED_ID;
    vpi_put_value(status, &val, 0, vpiNoDelay);
    fill_variable_with_x(callh);
    return 0;
  }

  /* Get the queue state and return it. */
  val.format = vpiIntVal;
  if (is_queue_full(idx))
    val.value.integer = 1;
  else
    val.value.integer = 0;
  vpi_put_value(callh, &val, 0, vpiNoDelay);

  /* The queue state was passed back so return OK. */
  val.format = vpiIntVal;
  val.value.integer = IVL_QUEUE_OK;
  vpi_put_value(status, &val, 0, vpiNoDelay);
  return 0;
}

/*
 * Check that the given $q_exam() call has valid arguments.
 */
static PLI_INT32 sys_q_exam_compiletf(ICARUS_VPI_CONST PLI_BYTE8 *name) {
  vpiHandle callh = vpi_handle(vpiSysTfCall, 0);
  vpiHandle argv = vpi_iterate(vpiArgument, callh);
  vpiHandle arg;

  /* Check that there are arguments. */
  if (argv == 0) {
    vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("%s requires four arguments.\n", name);
    vpi_control(vpiFinish, 1);
    return 0;
  }

  /* Check that the first two arguments (the id and code) are numeric. */
  if (check_numeric_args(argv, 2, callh, name)) return 0;

  /* The third argument (the value) must be a variable. */
  arg = vpi_scan(argv);
  if (!arg) {
    vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("%s requires a third (variable) argument.\n", name);
    vpi_control(vpiFinish, 1);
    return 0;
  }
  /* Check that the value argument is a variable with at least
   * 32 bits. */
  check_var_arg_large(arg, callh, name, "third");

  /* The fourth argument (the status) must be a variable. */
  arg = vpi_scan(argv);
  if (!arg) {
    vpi_printf("ERROR: %s:%d: ", vpi_get_str(vpiFile, callh),
               (int)vpi_get(vpiLineNo, callh));
    vpi_printf("%s requires a fourth (variable) argument.\n", name);
    vpi_control(vpiFinish, 1);
    return 0;
  }
  /* Check that the status argument is a 32 bit variable. */
  check_var_arg_32(arg, callh, name, "fourth");

  /* Make sure there are no extra arguments. */
  check_for_extra_args(argv, callh, name, "two arguments", 0);

  return 0;
}

/*
 * The runtime code for $q_exam().
 */
static PLI_INT32 sys_q_exam_calltf(ICARUS_VPI_CONST PLI_BYTE8 *name) {
  vpiHandle callh = vpi_handle(vpiSysTfCall, 0);
  vpiHandle argv = vpi_iterate(vpiArgument, callh);
  vpiHandle value, status;
  PLI_INT32 id, code, idx, rtn;
  s_vpi_value val;
  unsigned invalid_id, invalid_code;

  (void)name; /* Parameter is not used. */

  /* Get the id. */
  invalid_id = get_valid_32(vpi_scan(argv), &id);

  /* Get the code. */
  invalid_code = get_valid_32(vpi_scan(argv), &code);

  /* Get the value variable. */
  value = vpi_scan(argv);

  /* Get the status variable. */
  status = vpi_scan(argv);

  /* We are done with the argument iterator so free it. */
  vpi_free_object(argv);

  /* Verify that the id is valid. */
  idx = get_id_index(id);
  if (invalid_id || (idx < 0)) {
    fill_variable_with_x(value);
    val.format = vpiIntVal;
    val.value.integer = IVL_QUEUE_UNDEFINED_ID;
    vpi_put_value(status, &val, 0, vpiNoDelay);
    return 0;
  }

  /* Verify that the code is valid. */
  if (invalid_code || (code <= 0) || (code > 6)) {
    fill_variable_with_x(value);
    val.format = vpiIntVal;
    val.value.integer = IVL_QUEUE_UNDEFINED_STAT_CODE;
    vpi_put_value(status, &val, 0, vpiNoDelay);
    return 0;
  }

  rtn = IVL_QUEUE_OK;

  /* Calculate the requested queue information. */
  switch (code) {
      /* The current queue length. */
    case IVL_QUEUE_LENGTH:
      val.format = vpiIntVal;
      val.value.integer = get_current_queue_length(idx);
      vpi_put_value(value, &val, 0, vpiNoDelay);
      break;
      /* The mean inter-arrival time. */
    case IVL_QUEUE_MEAN:
      if (have_interarrival_statistic(idx) == 0) {
        fill_variable_with_x(value);
        rtn = IVL_QUEUE_NO_STATISTICS;
      } else {
        uint64_t ia_time = get_mean_interarrival_time(idx);
        rtn = fill_variable_with_scaled_time(value, ia_time);
      }
      break;
      /* The maximum queue length. */
    case IVL_QUEUE_MAX_LENGTH:
      val.format = vpiIntVal;
      val.value.integer = get_maximum_queue_length(idx);
      vpi_put_value(value, &val, 0, vpiNoDelay);
      break;
      /* The shortest queue wait time ever. */
    case IVL_QUEUE_SHORTEST:
      if (have_shortest_wait_statistic(idx) == 0) {
        fill_variable_with_x(value);
        rtn = IVL_QUEUE_NO_STATISTICS;
      } else {
        uint64_t sw_time = get_shortest_wait_time(idx);
        rtn = fill_variable_with_scaled_time(value, sw_time);
      }
      break;
      /* The longest wait time for elements still in the queue. */
    case IVL_QUEUE_LONGEST:
      if (get_current_queue_length(idx) == 0) {
        fill_variable_with_x(value);
        rtn = IVL_QUEUE_NO_STATISTICS;
      } else {
        uint64_t lq_time = get_longest_queue_time(idx);
        rtn = fill_variable_with_scaled_time(value, lq_time);
      }
      break;
      /* The average queue wait time. */
    case IVL_QUEUE_AVERAGE:
      if (have_average_wait_statistic(idx) == 0) {
        fill_variable_with_x(value);
        rtn = IVL_QUEUE_NO_STATISTICS;
      } else {
        uint64_t aw_time = get_average_wait_time(idx);
        rtn = fill_variable_with_scaled_time(value, aw_time);
      }
      break;
    default:
      assert(0);
  }

  /* The queue information was passed back so now return the status. */
  val.format = vpiIntVal;
  val.value.integer = rtn;
  vpi_put_value(status, &val, 0, vpiNoDelay);
  return 0;
}

/*
 * Routine to register the system tasks/functions provided in this file.
 */
void sys_queue_register(void) {
  s_vpi_systf_data tf_data;
  s_cb_data cb;
  vpiHandle res;

  tf_data.type = vpiSysTask;
  tf_data.tfname = "$q_initialize";
  tf_data.calltf = sys_q_initialize_calltf;
  tf_data.compiletf = sys_q_initialize_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$q_initialize";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  tf_data.type = vpiSysTask;
  tf_data.tfname = "$q_add";
  tf_data.calltf = sys_q_add_calltf;
  tf_data.compiletf = sys_q_add_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$q_add";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  tf_data.type = vpiSysTask;
  tf_data.tfname = "$q_remove";
  tf_data.calltf = sys_q_remove_calltf;
  tf_data.compiletf = sys_q_remove_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$q_remove";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  tf_data.type = vpiSysFunc;
  tf_data.sysfunctype = vpiSysFuncInt;
  tf_data.tfname = "$q_full";
  tf_data.calltf = sys_q_full_calltf;
  tf_data.compiletf = sys_q_full_compiletf;
  tf_data.sizetf = 0; /* Not needed for a vpiSysFuncInt. */
  tf_data.user_data = "$q_full";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  tf_data.type = vpiSysTask;
  tf_data.tfname = "$q_exam";
  tf_data.calltf = sys_q_exam_calltf;
  tf_data.compiletf = sys_q_exam_compiletf;
  tf_data.sizetf = 0;
  tf_data.user_data = "$q_exam";
  res = vpi_register_systf(&tf_data);
  vpip_make_systf_system_defined(res);

  /* Create a callback to clear all the queue memory when the
   * simulator finishes. */
  cb.time = NULL;
  cb.reason = cbEndOfSimulation;
  cb.cb_rtn = cleanup_queue;
  cb.user_data = 0x0;
  cb.obj = 0x0;

  vpi_register_cb(&cb);
}
